/*
 * 版权所有 (C) 2015 知启蒙(ZHIQIM) 保留所有权利。[遇见知启蒙，邂逅框架梦]
 * 
 * https://zhiqim.org/project/zhiqim_framework/zhiqim_kernel.htm
 *
 * Zhiqim Kernel is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package org.zhiqim.kernel.websocket;

import org.zhiqim.kernel.annotation.AnNonnull;
import org.zhiqim.kernel.annotation.AnNullable;
import org.zhiqim.kernel.model.mutables.MInt;
import org.zhiqim.kernel.util.Asserts;
import org.zhiqim.kernel.util.Bytes;
import org.zhiqim.kernel.util.Strings;

/**
 * WebSocket数据包格式：
 * 
 * RFC 6455
 * https://tools.ietf.org/html/rfc6455#section-5.2
 * 
 * 0                   1                   2                   3
 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 * +-+-+-+-+-------+-+-------------+-------------------------------+
 * |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
 * |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
 * |N|V|V|V|       |S|             |   (if payload len==126/127)   |
 * | |1|2|3|       |K|             |                               |
 * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 * |     Extended payload length continued, if payload len == 127  |
 * + - - - - - - - - - - - - - - - +-------------------------------+
 * |                               |Masking-key, if MASK set to 1  |
 * +-------------------------------+-------------------------------+
 * | Masking-key (continued)       |          Payload Data         |
 * +-------------------------------- - - - - - - - - - - - - - - - +
 * :                     Payload Data continued ...                :
 * + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 * |                     Payload Data continued ...                |
 * +---------------------------------------------------------------+
 *
 * 第一字节，第1位：FIN，标识是否为此消息的最后一个数据包
 * 第一字节，第2,3,4位保留
 * 第一字节，第5,6,7,8位，opcode，操作码（0－15）：
 * 0x00：标识一个中间数据包
 * 0x01：标识一个text类型数据包
 * 0x02：标识一个binary类型数据包
 * 0x08：标识一个断开连接类型数据包
 * 0x09：标识一个ping类型数据包
 * 0x0A：表示一个pong类型数据包
 * 0x03-07：保留
 * 0x0B-0F：保留
 * 
 * 第二字节，第1位：MASK，标识是否有掩码
 * 第二字节，第2-8位，标识有效数据长度（0-127）
 * 1、len < 126时，不需要扩展长度字段，len即数据长度
 * 2、len = 126时，扩展2字节表示数据长度
 * 3、len = 127时，扩展8字节表示数据长度
 * 
 * @version v1.0.0 @author zouzhigang 2017-6-1 新建与整理
 */
public class WebSocketPacket implements WebSocketConstants
{
    //1、标准字段
    protected boolean fin;
    protected int opcode;
    protected boolean mask;
    protected int len;
    
    //2、扩展字段
    protected int dataLen;        //数据长度
    protected byte[] maskingKey;  //掩码密钥
    protected byte[] data;        //数据
    
    /***********************************************************************/
    // 转换为数据包
    /***********************************************************************/
    
    /** 对象转数据包 */
    public byte[] toPacket()
    {//不加masks，长度＝前两字节+可能的长度字节+可能的掩码密钥+数据长度
        int length = 2 + ((len == TWO)?2:(len == EIGHT)?8:0) + (mask?4:0) + dataLen;
        byte[] buf = new byte[length];
        
        buf[0] = (byte)(fin?(0x80 | opcode):opcode);
        buf[1] = (byte)(mask?(0x80 | len):len);
        
        MInt off = new MInt(2);
        if (len == TWO)
            Bytes.BU.putShort(buf, off, dataLen);
        else if (len == EIGHT)
            Bytes.BU.putLong(buf, off, dataLen);
        
        if (mask)
        {//有掩码
            Bytes.putBytes(buf, off, maskingKey);
        }
        
        if (dataLen > 0)
        {//有数据
            if (!mask)
            {//无掩码
                Bytes.putBytes(buf, off, data);
            }
            else
            {//有掩码时对数据作数据转换
                byte[] mdata = new byte[dataLen];
                for (int i=0;i<dataLen;i++)
                {
                    mdata[i] = (byte)(data[i] ^ maskingKey[i % 4]);
                }
                Bytes.putBytes(buf, off, mdata);
            }
        }
        
        return buf;
    }
    
    /** 对象转字符串 */
    public String toString()
    {
        return new StringBuilder("WebSocketPacket[opcode:").append(opcode).append("]").toString();
    }
    
    /***********************************************************************/
    // 业务方法
    /***********************************************************************/
    
    public boolean isMiddle()
    {
        return opcode == MID;
    }
    
    public boolean isText()
    {
        return opcode == TEXT;
    }
    
    public boolean isBinary()
    {
        return opcode == BIN;
    }
    
    public boolean isClose()
    {
        return opcode == CLOSE;
    }
    
    public boolean isPing()
    {
        return opcode == PING;
    }
    
    public boolean isPong()
    {
        return opcode == PONG;
    }
   
    /** 数据长度是否双字节长度，无符号short，相当于int */
    public boolean isTwoLength()
    {
        return len == TWO;
    }
    
    /** 数据长度是否八字节长度，相当于len */
    public boolean isEightLength()
    {
        return len == EIGHT;
    }
    
    /***********************************************************************/
    // 获取
    /***********************************************************************/
    
    public boolean isFin()
    {
        return fin;
    }

    public int getOpcode()
    {
        return opcode;
    }

    public boolean isMask()
    {
        return mask;
    }

    @AnNullable
    public byte[] getMaskingKey()
    {
        return maskingKey;
    }

    public int getLen()
    {
        return len;
    }

    public int getDataLen()
    {
        return dataLen;
    }
    
    @AnNonnull
    public byte[] getData()
    {
        return (dataLen == 0)?new byte[0]:data;
    }
    
    public String getText()
    {
        if (!isText())
            throw Asserts.exception("WebSocketPacket[不是文本类型]");
        
        return (dataLen == 0)?_EMPTY_:Strings.newStringUTF8(data);
    }
    
    public int getCloseCode()
    {//数据前2字节为无符号整型的关闭代码，后2字节为关闭原因，这里取前两字节即可
        if (!isClose())
            throw Asserts.exception("WebSocketPacket[不是关闭类型]");
        
        return (dataLen == 0)?0:Bytes.BU.getShortUnsigned(data, 0);
    }

    /***********************************************************************/
    // 生成时设置
    /***********************************************************************/
    
    /** 设置掩码整型值 */
    public WebSocketPacket setMaskingKey(int maskingKey)
    {
        return setMaskingKey(Bytes.BU.toBytes(maskingKey));
    }
    
    /** 设置掩码 */
    public WebSocketPacket setMaskingKey(byte[] maskingKey)
    {
        this.mask = true;
        this.maskingKey = maskingKey;
        return this;
    }
    
    /** 生成PING数据包 */
    public WebSocketPacket setPing()
    {
        this.fin = true;
        this.opcode = PING;
        return this;
    }
    
    /** 生成PONG数据包 */
    public WebSocketPacket setPong()
    {
        this.fin = true;
        this.opcode = PONG;
        return this;
    }
    
    /** 生成CLOSE数据包 */
    public WebSocketPacket setClose(int code)
    {
        this.fin = true;
        this.opcode = CLOSE;
        this.calcData(Bytes.BU.toBytes((short)code));
        return this;
    }
    
    /** 设置文本数据 */
    public WebSocketPacket setText(String text)
    {
        this.fin = true;
        this.opcode = TEXT;
        this.calcData(text.getBytes(_UTF_8_C_));
        return this;
    }
    
    /** 设置二进制数据 */
    public WebSocketPacket setBinary(byte[] binary)
    {
        this.fin = true;
        this.opcode = BIN;
        this.calcData(binary);
        return this;
    }
    
    /** 计算数据长度 */
    private void calcData(byte[] data)
    {
        this.data = data;
        this.dataLen = data.length;

        if (dataLen < TWO)
            len = dataLen;
        else if (dataLen <= TWO_MAX)
            len = TWO;
        else
            len = EIGHT;
    }
}
